/*
 * Syntelos 'SX'
 * Copyright (C) 1998, 2007  John Pritchard.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package syntelos.sx;

import syntelos.lang.Jar;
import syntelos.sys.Statistics;

import alto.lang.Component;
import alto.lang.Type;
import alto.lang.Value;
import alto.sys.Reference;
import alto.sys.PHeaders;

/**
 * <p> Thread started by {@link syntelos.sys.Init}. </p>
 * 
 * @author jdp
 * @since 1.6
 */
public final class Server 
    extends ServerSocketFactory
{
    static {
        syntelos.net.Init.SInit();
    }
    public final static Jar Jar = new Jar(); 


    public final static int PORT_UNKNOWN = -1;
    public final static int PORT_HTTP = 80;
    public final static int PORT_HTTPM = 2997;
    public final static int PORT_HTTPS = 443;
    public final static int PORT_SIP = 5060;

    /**
     * @param protocol One of "HTTP", "HTTPS", "SIP" or "HTTPM".
     * @return Default port for protocol configuration string.
     */
    public final static int DefaultPort(java.lang.String protocol){
        if (null == protocol)
            return PORT_UNKNOWN;
        else if (protocol.startsWith("HTTP")){
            switch (protocol.length()){
            case 4:
                return PORT_HTTP;
            case 5:
                switch (protocol.charAt(4)){
                case 'M':
                    return PORT_HTTPM;
                case 'S':
                    return PORT_HTTPS;
                default:
                    return PORT_UNKNOWN;
                }
            default:
                return PORT_UNKNOWN;
            }
        }
        else if ("SIP".equals(protocol))
            return PORT_SIP;
        else
            return PORT_UNKNOWN;
    }
    /**
     * @param port Known port number, one of 80 (HTTP), 443 (HTTPS),
     * 9127 (HTTPR) or 5060 (SIP).
     * @return Configuration protocol string, one of "HTTP", "HTTPS",
     * "SIP" or "HTTPR", default "HTTP".
     */
    public final static java.lang.String ProtocolPort(int port){
        switch (port){
        case PORT_HTTP:
            return "HTTP";
        case PORT_HTTPS:
            return "HTTPS";
        case PORT_HTTPM:
            return "HTTPM";
        case PORT_SIP:
            return "SIP";
        default:
            return "HTTP";
        }
    }
    public final static int PortNum(PHeaders config){
        int portnum = config.getHeaderInt("Port",-1);
        if (0 < portnum)
            return portnum;
        else {
            return DefaultPort(config.getHeaderString("Protocol"));
        }
    }

    private final PHeaders configuration;

    private final java.net.InetSocketAddress port;

    private final Log log;

    private final java.net.URL shm_url;

    private java.lang.String scheme;

    private Shutdown shutdown;

    private Reference serverOpen;

    private Reference serverClose;

    private Reference requestIndex;

    private Component.Numeric request;

    private boolean shuttingdown = false;


    public Server(PHeaders config)
        throws java.io.IOException
    {
        super("Sys/SX/Server/"+config.getHeaderString("Protocol")+'/'+PortNum(config));
        this.configuration = config;
        this.port = this.createConfiguredAddress();
        this.shm_url = null;
        /*
         */
        this.log = new Log(this);
        Statistics.Instance.add(log.getStatistic());
        this.log.start();
        /*
         */
        this.shutdown = new Shutdown(this);
        java.lang.Runtime.getRuntime().addShutdownHook(this.shutdown);
    }

    /**
     * @return Server is generally employing a secure protocol.  
     */
    public final boolean isSecure(){
        return (PORT_HTTPS == this.getProtocolDefaultPort());
    }
    public final String getSecureEnabledCiphersString(){
        java.lang.String[] list = this.getSecureEnabledCiphers();
        if (null != list){
            java.lang.StringBuilder strbuf = new java.lang.StringBuilder();
            for (int cc = 0, count = list.length; cc < count; cc++){
                if (0 < strbuf.length())
                    strbuf.append(',');
                strbuf.append(list[cc]);
            }
            return strbuf.toString();
        }
        return null;
    }
    public final String getSecureEnabledProtocolsString(){
        java.lang.String[] list = this.getSecureEnabledProtocols();
        if (null != list){
            java.lang.StringBuilder strbuf = new java.lang.StringBuilder();
            for (int cc = 0, count = list.length; cc < count; cc++){
                if (0 < strbuf.length())
                    strbuf.append(',');
                strbuf.append(list[cc]);
            }
            return strbuf.toString();
        }
        return null;
    }
    public final Log getLog(){
        return this.log;
    }
    public final void logWrite(Request request, Response response){
        this.log.pushWrite(request,response);
    }
    public final void logWrite(Request request, Response response, java.lang.Throwable error){
        this.log.pushWrite(request,response,error);
    }
    public final java.lang.String getProtocol(){
        return this.configuration.getHeaderString("Protocol");
    }
    public final java.lang.String getProtocolScheme(){
        java.lang.String scheme = this.scheme;
        if (null == scheme){
            scheme = this.getProtocol().toLowerCase();
            synchronized(this){
                this.scheme = scheme;
            }
        }
        return scheme;
    }
    public final int getProtocolDefaultPort(){
        return DefaultPort(this.getProtocol());
    }
    public int getConfiguredPort(){
        Integer value = this.configuration.getHeaderInteger("Port");
        if (null != value)
            return value.intValue();
        else 
            return this.getProtocolDefaultPort();
    }
    public java.lang.String getConfiguredInterface(){
        return this.configuration.getHeaderString("Interface");
    }
    private java.net.InetSocketAddress createConfiguredAddress(){
        int port = this.getConfiguredPort();
        String inf = this.getConfiguredInterface();
        if (null != inf)
            return new java.net.InetSocketAddress(inf,port);
        else
            return new java.net.InetSocketAddress(port);
    }
    public final boolean isLogEnabled(){
        return this.configuration.getHeaderBool("Log",true);
    }
    public final boolean isLogEcho(){
        return this.configuration.getHeaderBool("Log-Echo",false);
    }
    public final int getPort(){
        return this.port.getPort();
    }
    public final java.net.InetSocketAddress getInterface(){
        return this.port;
    }
    public final Component.Host getPortAddress(){
        return alto.lang.Component.Host.Tools.ValueOf(this.port.getAddress().getAddress());
    }
    public final Reference getServerReferenceOpen(){
        return this.serverOpen;
    }
    public final Reference getServerReferenceClose(){
        return this.serverClose;
    }
    public final boolean isShm(){
        return (null != this.shm_url);
    }
    public final java.net.URL getShmUrl(){
        return this.shm_url;
    }
    private synchronized Socket init()
        throws java.io.IOException
    {
        Service.Init();

        Component.Host container = this.getPortAddress();
        /*
         * Read server log id
         */
        Reference reference_server_index = new syntelos.sys.Reference(container,Type.Tools.Of("sx-log-server"),0);
        Component.Numeric server_index = Component.Path.Tools.Dereference(reference_server_index);
        if (null == server_index)
            server_index = Component.Path.ZERO;

        server_index = server_index.add(Component.Path.ONE);
        this.serverOpen = new syntelos.sys.Reference(container,Type.Tools.Of("sx-log-server"),server_index);

        server_index = server_index.add(Component.Path.ONE);
        this.serverClose = new syntelos.sys.Reference(container,Type.Tools.Of("sx-log-server"),server_index);

        reference_server_index.setBufferFrom(server_index);
        /*
         * Read request log id
         */
        this.requestIndex = new syntelos.sys.Reference(container,Type.Tools.Of("sx-log-http"),0);

        Component.Numeric request_index = Component.Path.Tools.Dereference(this.requestIndex);
        if (null == request_index)
            request_index = Component.Path.ZERO;

        this.request = request_index;
        /*
         */
        return this.create();
    }
    public final synchronized Component requestSequence(){
        Component.Numeric re = this.request.add(Component.Path.ONE);
        this.request = re;
        return re;
    }
    public final synchronized void onshutdown(){
        this.shuttingdown = true;
        /*
         */
        this.log.pushClose(this);
        /*
         */
        Component.Numeric index = this.request;
        if (null != index){
            try {
                this.requestIndex.setBufferFrom(index);
            }
            catch (java.io.IOException exc){
                exc.printStackTrace();//(punt!)
            }
        }
        if (null != this.shutdown){
            try {
                java.lang.Runtime.getRuntime().removeShutdownHook(this.shutdown);
            }
            catch (java.lang.IllegalStateException ignoreShutdownInProgress){
            }
            finally {
                this.shutdown = null;
            }
        }
    }
    public final void run(){
        Socket server = null;
        try {
            server = this.init();
            this.log.pushOpen(this);
            /*
             */
            Socket client = null;
            while (!this.shuttingdown){
                try {
                    while (!this.shuttingdown){
                        client = (Socket)server.accept();
                        Socket.Queue.Push(client);
                        client = null;
                    }
                }
                catch (alto.sys.ServiceUnavailableException error){
                    Response response = null;
                    try {
                        response = Response.ServiceUnavailable().cloneResponse();
                        response.error(client);
                        response.write();
                    }
                    catch (Throwable neverdie){
                    }
                    finally {
                        if (client.isNotShm()){
                            try {
                                client.close();
                            }
                            catch (java.lang.Throwable t3){
                            }
                        }
                        if (null != response)
                            response.close();
                        response = null;
                    }
                }
                catch (java.lang.Throwable t){
                    java.lang.System.gc();
                    synchronized(System.err){
                        System.err.print(this.getName()+' ');
                        t.printStackTrace(System.err);
                    }
                    if (null != client){
                        Response response = null;
                        try {
                            response = Response.ServiceUnavailable().cloneResponse();
                            response.error(client);
                            response.write();
                        }
                        catch (java.lang.Throwable t2){
                        }
                        finally {
                            if (client.isNotShm()){
                                try {
                                    client.close();
                                }
                                catch (java.lang.Throwable t3){
                                }
                            }
                            if (null != response)
                                response.close();
                            response = null;
                        }
                    }
                }
            }
        }
        catch (java.lang.Exception exc){
            /*
             * Server init problem
             */
            java.io.PrintStream err = System.err;
            synchronized(err){
                err.print(this.getName()+' ');
                exc.printStackTrace();
            }
        }
        finally {
            if (null != server){
                try {
                    /*
                     * This is the place to close SHM sockets.
                     */
                    server.close();
                }
                catch (java.io.IOException ignore){
                }
            }
            Shutdown shutdown = this.shutdown;
            if (null != shutdown && java.lang.Thread.currentThread() != shutdown)
                this.onshutdown();
        }
    }
}
